/*
  Copyright (c) 2008-2013  John Lee (j.y.lee@yeah.net)
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.

  * Neither the name of the copyright holders nor the names of
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef __LIGHTWEIGHT_OBJECT_ORENTED_KERNEL_HPP
#error "Include <look.h> or <look.hpp> instead of this file."
#elif !defined(__GNUG__)
#error This header file must only be compiled by GNU C++ compiler.
#elif (__GNUC__ * 0x10000 + __GNUC_MINOR__ * 0x100 + __GNUC_PATCHLEVEL__) < 0x040801
#error GCC 4.8.1 or greater is required.
#elif !defined(__ARM_ARCH_6M__) && !defined(__ARM_ARCH_7M__)
#error This header file must only be compiled for ARMv6-M and ARMv7-M architecture.
#else

#ifndef __LOOK_INTERFACE
#define __LOOK_INTERFACE

#pragma interface

#include <cstdint>

#define __LOOK_VERSION__  0x20131210UL

#define LOOK_HWINIT()    LOOK_HWINIT_PRIORITY(5)
#define LOOK_HWINIT_PRIORITY(PRIORITY)  __LOOK_HWINIT(__COUNTER__, PRIORITY)

#if !defined(__DOXYGEN__)
#ifndef __CONCAT
#define __CONCAT(a, b)    __CONCAT_I(a, b)
#define __CONCAT_I(a, b)  a ## b
#endif  // __CONCAT

#define __LOOK_HWINIT(CNT, PRIORITY)          \
  [[gnu::constructor(101 + PRIORITY)]] static void __CONCAT(look_hwinit_, CNT)()

#define LOOK_SVC(_N, _F, ...)                 \
  ({                                          \
    register auto f asm ("ip") = _F;          \
    register uintptr_t result asm ("r0");     \
    __asm__ __volatile__(                     \
      "svc %[n]"                              \
      : "=r" (result)                         \
      : [n] "i" (_N), "r" (f), ##__VA_ARGS__  \
      : "memory"                              \
    );                                        \
    result;                                   \
  })
#endif  // !__DOXYGEN__

namespace look {
namespace helper {
[[gnu::always_inline]] inline static uintptr_t svc (uintptr_t n, void (*func) ())
{
  return LOOK_SVC (n, func);
}

[[gnu::always_inline]] inline static uintptr_t svc (uintptr_t n, void (*func) (), uintptr_t a1)
{
  register auto _a1 asm ("a1") = a1;
  return LOOK_SVC (n, func, "r" (_a1));
}

[[gnu::always_inline]] inline static uintptr_t svc (uintptr_t n, void (*func) (), uintptr_t a1, uintptr_t a2)
{
  register auto _a1 asm ("a1") = a1;
  register auto _a2 asm ("a2") = a2;
  return LOOK_SVC (n, func, "r" (_a1), "r" (_a2));
}

[[gnu::always_inline]] inline static uintptr_t svc (uintptr_t n, void (*func) (), uintptr_t a1, uintptr_t a2, uintptr_t a3)
{
  register auto _a1 asm ("a1") = a1;
  register auto _a2 asm ("a2") = a2;
  register auto _a3 asm ("a3") = a3;
  return LOOK_SVC (n, func, "r" (_a1), "r" (_a2), "r" (_a3));
}

[[gnu::always_inline]] inline static uintptr_t svc (uintptr_t n, void (*func) (), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4)
{
  register auto _a1 asm ("a1") = a1;
  register auto _a2 asm ("a2") = a2;
  register auto _a3 asm ("a3") = a3;
  register auto _a4 asm ("a4") = a4;
  return LOOK_SVC (n, func, "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4));
}
}

template <uint8_t N, typename Result>
[[gnu::always_inline]] inline static Result svc (Result (*func) ())
{ return Result (helper::svc (N, reinterpret_cast <void (*) ()> (func))); }

template <uint8_t N, typename Result, typename... Params, typename A1>
[[gnu::always_inline]] inline static Result svc (Result (*func) (Params...), A1&& a1)
{
  return Result (helper::svc (N, reinterpret_cast <void (*) ()> (func),
    uintptr_t (a1)));
}

template <uint8_t N, typename Result, typename... Params, typename A1, typename A2>
[[gnu::always_inline]] inline static Result svc (Result (*func) (Params...), A1&& a1, A2&& a2)
{
  return Result (helper::svc (N, reinterpret_cast <void (*) ()> (func),
    uintptr_t (a1), uintptr_t (a2)));
}

template <uint8_t N, typename Result, typename... Params, typename A1, typename A2, typename A3>
[[gnu::always_inline]] inline static Result svc (Result (*func) (Params...), A1&& a1, A2&& a2, A3&& a3)
{
  return Result (helper::svc (N, reinterpret_cast <void (*) ()> (func),
    uintptr_t (a1), uintptr_t (a2), uintptr_t (a3)));
}

template <uint8_t N, typename Result, typename... Params, typename A1, typename A2, typename A3, typename A4>
[[gnu::always_inline]] inline static Result svc (Result (*func) (Params...), A1&& a1, A2&& a2, A3&& a3, A4&& a4)
{
  return Result (helper::svc (N, reinterpret_cast <void (*) ()> (func),
    uintptr_t (a1), uintptr_t (a2), uintptr_t (a3), uintptr_t (a4)));
}

template <typename Result, typename T, typename... Params, typename... Args>
[[gnu::always_inline]] inline static Result svc (Result (T::*func) (Params...), T* const t, uintptr_t timeout, Args&&... args)
{
  return svc <sizeof... (args) + 1> (reinterpret_cast <Result (*) (T* const, Params...)> (func),
    t, args..., timeout);
}

template <typename Result, typename T, typename... Params, typename... Args>
[[gnu::always_inline]] inline static Result svc (Result (T::*func) (Params...), T* const t, Args&&... args)
{
  return svc <0> (reinterpret_cast <Result (*) (T* const, Params...)> (func),
    t, args...);
}

constexpr uint_fast32_t operator "" _msec (uint64_t msec)
{ return (msec * LOOK_TICKS_PER_SEC + 999) / 1000; }

constexpr uint_fast32_t operator "" _msec (long double msec)
{ return uint_fast32_t ((msec * LOOK_TICKS_PER_SEC + 999) / 1000); }

constexpr uint_fast32_t operator "" _sec (uint64_t sec)
{ return sec * LOOK_TICKS_PER_SEC; }

constexpr uint_fast32_t operator "" _sec (long double sec)
{ return uint_fast32_t (sec * LOOK_TICKS_PER_SEC); }

constexpr uint_fast32_t operator "" _min (uint64_t min)
{ return min * LOOK_TICKS_PER_SEC * 60; }

constexpr uint_fast32_t operator "" _min (long double min)
{ return uint_fast32_t (min * LOOK_TICKS_PER_SEC * 60); }

constexpr uint_fast32_t operator "" _hour (uint64_t hour)
{ return hour * LOOK_TICKS_PER_SEC * 3600; }

constexpr uint_fast32_t operator "" _hour (long double hour)
{ return uint_fast32_t (hour * LOOK_TICKS_PER_SEC * 3600); }

class interrupt_t;
class systick_t;
extern systick_t systick;

class vector_t {
  friend interrupt_t;
  friend systick_t;

public:
  [[gnu::always_inline]] inline static void enable (uint_fast8_t irq)
  { reinterpret_cast <volatile uint32_t*> (0xe000e100)[irq >> 5] = 1 << (irq & 31); }

  [[gnu::always_inline]] inline static void disable (uint_fast8_t irq)
  { reinterpret_cast <volatile uint32_t*> (0xe000e180)[irq >> 5] = 1 << (irq & 31); }

  [[gnu::always_inline]] inline static bool get_pending (uint_fast8_t irq)
  { return (reinterpret_cast <volatile uint32_t*> (0xe000e200)[irq >> 5] & (1 << (irq & 31))) != 0; }

  [[gnu::always_inline]] inline static void set_pending (uint_fast8_t irq)
  { reinterpret_cast <volatile uint32_t*> (0xe000e200)[irq >> 5] = 1 << (irq & 31); }

  [[gnu::always_inline]] inline static void clear_pending (uint_fast8_t irq)
  { reinterpret_cast <volatile uint32_t*> (0xe000e280)[irq >> 5] = 1 << (irq & 31); }

  [[gnu::always_inline]] inline static void set_priority (uint_fast8_t irq, uint_fast8_t priority)
  { reinterpret_cast <volatile uint8_t*> (0xe000e400)[irq] = priority; }

private:
  template <int Vector, void Isr (void)>
  [[gnu::always_inline]] inline static void attach ()
  {
    __asm__(
      ".pushsection .vector.%c[v],\"aw\",%%progbits"  "\n\t"
      ".global __vector_%c[v]"    "\n"
      "__vector_%c[v]:"           "\n\t"
      ".word %c[r]"               "\n\t"
      ".popsection"
      : : [v] "i" (Vector + 16), [r] "i" (Isr)
    );
  };

  template <int Vector>
  [[gnu::always_inline]] inline static void attach (interrupt_t* intr)
  {
    __asm__(
      ".pushsection .intr.%c[v],\"aw\",%%progbits"    "\n\t"
      ".global __intr_%c[v]"      "\n"
      "__intr_%c[v]:"             "\n\t"
      ".word %c[r]"               "\n\t"
      ".popsection"
      : : [v] "i" (Vector + 16), [r] "i" (intr)
    );
  };

private:
  static void reset ();
  static void libc_reset ();
  static void dsr ();
  static void nmi ();
  static void hard_fault ();
  static void vsr ();

#ifdef __ARM_ARCH_7M__
  static void mem_manage ();
  static void bus_fault ();
  static void usage_fault ();
  static void debug_monitor ();
#endif  // __ARM_ARCH_7M__
};

class sink_t {
public:
  [[gnu::always_inline]] inline sink_t () { }
  virtual void on_signal (void* addr, uintptr_t signal);
};

class interrupt_t : public sink_t {
public:
  [[gnu::always_inline]] inline interrupt_t () { }

  template <int Vector, void Isr (void) = vector_t::vsr>
  [[gnu::always_inline]] inline static void attach (interrupt_t* intr)
  {
    vector_t::attach <Vector, Isr> ();
    vector_t::attach <Vector> (intr);
  }

private:
  virtual bool isr (int vector) = 0;
  virtual void dsr () = 0;

private:
  interrupt_t* next;
};

class timer_t {
public:
  [[gnu::always_inline]] inline timer_t (sink_t& sink) : sink{ &sink }, next{ nullptr } { }
  [[gnu::always_inline]] inline uintptr_t start (uintptr_t interval);
  [[gnu::always_inline]] inline uintptr_t kill () { return start (0); }
  [[gnu::always_inline]] inline uintptr_t do_start (uintptr_t interval);
  [[gnu::always_inline]] inline uintptr_t do_kill () { return do_start (0); }
  [[gnu::always_inline]] inline bool is_active () const { return next != nullptr; }

private:
  uintptr_t count;
  sink_t* sink;
  timer_t* next;
  timer_t* prev;
};

class systick_t : public interrupt_t {
public:
  [[gnu::always_inline]] inline systick_t (uint_fast32_t reload, uint_fast8_t priority = 0)
  {
    *reinterpret_cast <volatile uint8_t*> (0xe000ed23) = priority;
    volatile uint32_t* scs = reinterpret_cast <volatile uint32_t*> (0xe000e000);
    __asm__ ("" : "+r" (scs));
    scs[6] = reload;
    scs[5] = reload;
    scs[4] = 7;
  }

  [[gnu::always_inline]] inline uintptr_t get_jiffies () const { return jiffies; }
  [[gnu::always_inline]] inline uintptr_t set_timer (timer_t& timer, uintptr_t interval)
  { return svc (&systick_t::do_set_timer, this, &timer, interval); }

  [[gnu::always_inline]] inline uintptr_t kill_timer (timer_t& timer)
  { return set_timer (timer, 0); }

  uintptr_t do_set_timer (timer_t& timer, uintptr_t interval);

protected:
  bool isr (int vector) override;
  void dsr () override;

private:
  volatile uintptr_t jiffies;
  timer_t* wheel[8];
};

[[gnu::always_inline]] inline uintptr_t timer_t::start (uintptr_t interval)
{ return systick.set_timer (*this, interval); }

[[gnu::always_inline]] inline uintptr_t timer_t::do_start (uintptr_t interval)
{ return systick.do_set_timer (*this, interval); }

class idle_task_t : public sink_t {
public:
  enum {
    MIN_CONTEXT_SIZE = 16
  };

  void init (uintptr_t& context, void* routine);

protected:
  [[gnu::always_inline]] inline idle_task_t () { }

private:
  uintptr_t* context;
  uintptr_t info;
};

extern idle_task_t idle_task;
class sync_t;

namespace base {
class task_t : public idle_task_t {
public:
  [[gnu::always_inline]] inline bool wakeup ()
  { return svc (&task_t::do_wakeup, this); }

  bool do_wakeup ();

protected:
  [[gnu::always_inline]] inline task_t () { }

protected:
  void on_signal (void* addr, uintptr_t signal);
};

class sched_t : public sink_t {
  friend vector_t;
  friend sync_t;

public:
  [[gnu::always_inline]] inline void lock ()
  {
    uintptr_t cnt = lock_count;
    cnt++;
    lock_count = cnt;
  }

  [[gnu::always_inline]] inline void unlock ()
  {
    uintptr_t cnt = lock_count - 1;
    if (cnt != 0)
      lock_count = cnt;
    else
      unlock_reschedule ();
  }

  [[gnu::always_inline]] inline void unlock_reschedule ()
  { svc (&sched_t::do_unlock_reschedule, this); }

  [[gnu::always_inline]] inline void reschedule ()
  { svc (&sched_t::do_reschedule, this); }

  void do_reschedule ();
  void do_unlock_reschedule ();

  [[gnu::always_inline]] inline task_t& get_current_task ()
  { return *cur_task; }

protected:
  [[gnu::always_inline]] inline sched_t () : lock_count{ 1 }, data{ 1 } { }

#ifndef __DOXYGEN__
  task_t* do_wakeup (sync_t& sync, task_t* task, uintptr_t result);
#endif  // __DOXYGEN__

protected:
  virtual task_t* dispatch () = 0;
  virtual task_t* ready (sync_t& sync, task_t* task = nullptr) = 0;
  virtual void block (sync_t& sync) = 0;

private:
  static void svcall ();
  static void pendsv ();

protected:
  task_t* cur_task;
  uint16_t lock_count;
  uint16_t data;
};
}

class sync_t {
public:
  [[gnu::always_inline]] inline sync_t () : wait_queue{ 0 } { }
  [[gnu::always_inline]] inline void block (uintptr_t timeout = 0)
  { svc (&sync_t::do_block, this, timeout); }

  void do_block ();

protected:
  [[gnu::always_inline]] inline base::task_t* wakeup (uintptr_t data, base::task_t* task = nullptr);
  [[gnu::always_inline]] inline base::task_t* do_wakeup (uintptr_t data, base::task_t* task = nullptr);

protected:
  uintptr_t wait_queue;
};

class sem_t : public sync_t {
public:
  [[gnu::always_inline]] inline sem_t (uintptr_t cnt = 0) : count{ cnt } { }
  [[gnu::always_inline]] inline uintptr_t get_count () const { return count; }

#ifdef __ARM_ARCH_6M__
  [[gnu::always_inline]] inline bool trywait ()
  { return svc (&sem_t::do_trywait, this); }
#else  // __ARM_ARCH_6M__
  bool trywait ();
#endif  // __ARM_ARCH_6M__

  [[gnu::always_inline]] inline bool wait (uintptr_t timeout = 0)
  { return svc (&sem_t::do_trywait, this, timeout); }

  [[gnu::always_inline]] inline void post ()
  { svc (&sem_t::do_post, this); }

  bool do_trywait ();
  bool do_post ();

#ifndef __DOXYGEN__
private:
  uintptr_t count;
#endif  // __DOXYGEN__
};

namespace base {
class mbox_t : public sync_t {
public:
  [[gnu::always_inline]] inline uint_fast8_t get_entries () const { return entries; }

protected:
  [[gnu::always_inline]] inline mbox_t (uint8_t size) : size{ size }, in{ 0 }, out{ 0 }, entries{ 0 } { }

#ifndef __DOXYGEN__
  [[gnu::always_inline]] inline bool tryget (uintptr_t& msg)
  { return svc (&base::mbox_t::do_tryget, this, &msg); }

  [[gnu::always_inline]] inline bool get (uintptr_t& msg, uintptr_t timeout = 0)
  { return svc (&base::mbox_t::do_tryget, this, timeout, &msg); }

  [[gnu::always_inline]] inline bool tryput (uintptr_t msg, bool front)
  { return svc (&base::mbox_t::do_tryput, this, msg, front); }

  [[gnu::always_inline]] inline bool put (uintptr_t msg, bool front, uintptr_t timeout = 0)
  { return svc (&base::mbox_t::do_tryput, this, msg, front, timeout); }

  bool do_tryget (uintptr_t& msg);
  bool do_tryput (uintptr_t msg, bool front);

protected:
  uint8_t size;
  uint8_t in;
  uint8_t out;
  uint8_t entries;
#endif  // __DOXYGEN__
};
}

template <typename Queue, typename Msg, uint8_t Size>
class queue_t : public Queue {
public:
  [[gnu::always_inline]] inline uint_fast8_t get_size () const { return Size; }
  [[gnu::always_inline]] inline bool peek (Msg& msg) const
  {
    if (Queue::entries) {
      msg = buffer[Queue::out];
      return true;
    }
    return false;
  }

  [[gnu::always_inline]] inline bool tryget (Msg& msg)
  { return Queue::tryget (reinterpret_cast <uintptr_t&> (msg)); }

  [[gnu::always_inline]] inline bool get (Msg& msg, uintptr_t timeout = 0)
  { return Queue::get (reinterpret_cast <uintptr_t&> (msg), timeout); }

  [[gnu::always_inline]] inline bool tryput (Msg msg)
  { return Queue::tryput (uintptr_t (msg), false); }

  [[gnu::always_inline]] inline bool tryput_front (Msg msg)
  { return Queue::tryput (uintptr_t (msg), true); }

  [[gnu::always_inline]] inline bool put (Msg msg, uintptr_t timeout = 0)
  { return Queue::put (uintptr_t (msg), false, timeout); }

  [[gnu::always_inline]] inline bool put_front (Msg msg, uintptr_t timeout = 0)
  { return Queue::put (uintptr_t (msg), true, timeout); }

  [[gnu::always_inline]] inline bool do_tryget (Msg& msg)
  { return Queue::do_tryget (reinterpret_cast <uintptr_t&> (msg)); }

  [[gnu::always_inline]] inline bool do_tryput (Msg msg)
  { return Queue::do_tryput (uintptr_t (msg), false); }

  [[gnu::always_inline]] inline bool do_tryput_front (Msg msg)
  { return Queue::do_tryput (uintptr_t (msg), true); }

protected:
  [[gnu::always_inline]] inline queue_t () : Queue (Size) { }

#ifndef __DOXYGEN__
private:
  Msg buffer[Size];
#endif  // __DOXYGEN__
};

template <typename Msg, uint8_t Size>
class mbox_t : public queue_t <base::mbox_t, Msg, Size> {
  static_assert (sizeof (Msg) == sizeof (uintptr_t), "the type argument for <Msg> must be same size as uintptr_t");
  static_assert (Size > 0 && Size < 256, "<Size> must be greater than 0 and less than 256");

public:
  [[gnu::always_inline]] inline mbox_t () : queue_t <base::mbox_t, Msg, Size> () { }
};

namespace base {
class flag_t : public sync_t {
public:
  enum mode_t {
    ANY_KEEP,
    ANY_CONSUME,
    ALL_KEEP,
    ALL_CONSUME,
    LSB_KEEP,
    LSB_CONSUME
  };

public:
  [[gnu::always_inline]] inline uintptr_t peek () const { return flags; }

#ifdef __ARM_ARCH_6M__
  [[gnu::always_inline]] inline uintptr_t poll (uintptr_t pattern, mode_t mode)
  { return svc (&flag_t::do_poll, this, pattern, mode); }

  [[gnu::always_inline]] inline void mask_bits (uintptr_t pattern)
  { svc (&flag_t::do_mask_bits, this, pattern); }
#else  // __ARM_ARCH_6M__
  uintptr_t poll (uintptr_t pattern, mode_t mode);
  void mask_bits (uintptr_t pattern);
#endif  // __ARM_ARCH_6M__

  [[gnu::always_inline]] inline uintptr_t wait (uintptr_t pattern, mode_t mode, uintptr_t timeout = 0)
  { return svc (&flag_t::do_poll, this, timeout, pattern, mode); }

  uintptr_t do_poll (uintptr_t pattern, mode_t mode);
  void do_mask_bits (uintptr_t pattern);

protected:
  [[gnu::always_inline]] inline flag_t (uintptr_t flags) : flags{ flags } { }

protected:
  uintptr_t flags;
};
}

namespace link {
class flag_t : public base::flag_t {
public:
  [[gnu::always_inline]] inline flag_t (uintptr_t flags = 0) : base::flag_t (flags) { }
  [[gnu::always_inline]] inline void set_bits (uintptr_t pattern)
  { svc (&flag_t::do_set_bits, this, pattern); }

  void do_set_bits (uintptr_t pattern);
};

class task_t : public base::task_t {
protected:
  [[gnu::always_inline]] inline task_t () { }

private:
  task_t* next;
  task_t* prev;
};
}

extern sync_t sync;

[[gnu::always_inline]] inline void delay (uintptr_t timeout = 0)
{ sync.block (timeout); }

extern uintptr_t version;

[[gnu::always_inline]] inline uintptr_t get_version ()
{ return reinterpret_cast <uintptr_t> (&version); }

#ifdef __ARM_ARCH_7M__
bool do_set_privilege (bool state);
[[gnu::always_inline]] inline bool set_privilege (bool state)
{ return svc <0> (&do_set_privilege, state); }
#endif  // __ARM_ARCH_7M__
}
#else  // __LOOK_INTERFACE
#ifndef __LOOK_INLINE
#define __LOOK_INLINE

[[gnu::always_inline]] inline look::base::task_t* look::sync_t::wakeup (uintptr_t data, base::task_t* task)
{ return svc (&sched_t::do_wakeup, static_cast <base::sched_t* const> (&scheduler), this, task, data); }

[[gnu::always_inline]] inline look::base::task_t* look::sync_t::do_wakeup (uintptr_t data, base::task_t* task)
{ return scheduler.do_wakeup (*this, task, data); }
#endif  // __LOOK_INLINE
#endif  // __LOOK_INTERFACE
#endif  // __LIGHTWEIGHT_OBJECT_ORENTED_KERNEL_HPP
